import java.util.Random;
import java.util.ArrayList;
import java.io.*;

public class TC3 {
  
  private NeuralNet substrate;
  private NeuralNet cppn;
  private static double[][][] training;
  private static double[][][] test;
  BufferedWriter w;

  public TC3() {
    String[][] layout = {{"11", "11", "L"}, {"9", "9", "S"}, {"1", "1", "SC"}};
    substrate = new NeuralNet(NeuralNet.quickSubstrate(layout), 0);
		String[][] layoutC = {{"B", "L", "L", "L", "L", "L", "L"}, {"S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"},  {"S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"}, {"SS"}};
    cppn = new NeuralNet(NeuralNet.quickCPPN(layoutC), 1);
    training = full(); //sample(300);
    test = full();
    try {
      w = new BufferedWriter(new FileWriter("RX/r"+(System.currentTimeMillis()/(60*1000))%1000+".csv"));
    } catch (IOException e) {
      System.err.println(e.getMessage());
    }
  }

  public static void main(String[] args) {
    TC3 d = new TC3();
    //for (int i = 0; i < d.net.size(1); i++) d.net.getNode(1, i).setWeight(0, 0.1);
    
    int c = 0;
    /*double e = Double.POSITIVE_INFINITY;
    while (e > 0.2){
      e = d.substrate.trainNetwork(test, 0.1) / test.length;
      System.out.println(c+ "\t"+ e);
      c++;
    }*/
    d.interleaved(100);
    try {
      d.w.close();
    } catch (IOException e) {
      System.err.println(e.getMessage());
    }

    c = 0;
    System.out.println("\n\n");//+d.net.evaluate(test) / test.length);
    for (int i = 0; i < test.length; i++) {
      d.substrate.processInput(test[i][0]);
      //c += (int) Math.abs(Math.round(d.net.getOutputs()[0]) - test[i][1][0]);
      if (Math.round(d.substrate.getOutputs()[0]) == test[i][1][0]) c++;
    }
    System.out.println(c +"\t"+ test.length +"\t"+ ((double) c  / test.length));
  }
  
  public static double[][][] sample(int a) {
    double[][][] r = new double[a][3][];
    Random g = new Random();
    for (int i = 0; i < a; i++) {
      int x = g.nextInt(9);
      int y = g.nextInt(9);
      int t = g.nextInt(2);
      int e = g.nextInt(4);
      r[i][0] = data(x,y,t);
      r[i][1] = new double[1];
      r[i][1][0] = t;  
      r[i][2] = new double[1];
    }
    return r;
  }


  public static double[][][] full() {
    double[][][] r = new double[9*9*2][3][];
    for (int x = 0; x < 9; x++) {
      for (int y = 0; y < 9; y++) {
        for (int t = 0; t < 2; t++) {
          int i = x*(9*2) + y*(2) + t;
          r[i][0] = data(x,y,t);
          r[i][1] = new double[1];
          r[i][1][0] = t;  
          r[i][2] = new double[1];
        }
      }
    }
    return r;
  }

  public static double[] data(int x, int y, int t) {
    int[][] s  =    {{1, 0, 1,
                      0, 1, 0,
                      1, 0, 1},

                     {1, 1, 1,
                      1, 0, 1,
                      1, 1, 1}};

    double[] r = new double[121];
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 3; j++) {
        r[(x+i)*11 + y+j] = s[t][i*3 + j];
      }
    }
    return r;
  }


	public void interleaved(int thres) {
		int cppnFactor = 15;
    int count = 0;
		double e = 0;
    double ex = 0;
    double et = 0;
    double ep = 0;
    long time = System.currentTimeMillis();
		setSubstrateWeights(cppn, substrate);
		et = substrate.evaluate(training) / training.length;
		do {
			//for (int i = 0; i < thres; i++) {
			/*substrate.trainNetwork(training, 0.1);
			ex = substrate.evaluate(training) / training.length;
      et = ex;
			System.out.println("Substrate: "+ ex);*/
      int c = 0;
      e = et;
      for (int i = 0; i < thres; i++) {
      //do {
				substrate.trainNetwork(training, 0.1);
        ep = e;
				e = substrate.evaluate(training) / training.length;
        if (e > ep) System.err.println("Substrate learning rate fail!");
				System.out.println("Substrate " + c + "\t: "+ e +"\t"+et+"\t"+(ex-et)); 
        try {
          w.write(e +", "+ et +"\n");
        } catch (IOException excp) {
          System.err.println(excp.getMessage());
        }
        c++;
			} //while (ex - e < 0.03);
      double cppnLearnRate = 0.1;
			double[][][] cppnData = cppnData(substrate);
      double en = cppn.trainNetwork(cppnData, cppnLearnRate) / cppnData.length;
      System.out.println("CPPN: "+ en);
			for (int i = 0; i < c*cppnFactor; i++) {
				e = cppn.trainNetwork(cppnData, cppnLearnRate) / cppnData.length;
        if (e > en) System.err.println("CPPN learning rate fail!");
        en = e;
			} //while(e > 0.99*en);
      System.out.println("CPPN: "+e);
			setSubstrateWeights(cppn, substrate);
      ex = et;
      et = substrate.evaluate(training) / training.length;
      /*if (c % 7 == 0) {
        substrate.processInput(training[600][0]);
        boxWrite(substrate.getOutputs(), "Res/output_"+c);
      }*/
      System.out.println(et);
      count++;
		} while (count < 15);
	}

  /*Both functions are fixed to only process the first layer*/

	//Note: No fix for bias nodes yet
	public double[][][] cppnData(NeuralNet net) {
		ArrayList<double[][]> d = new ArrayList<double[][]>();
		for (int i = 0; i < 1; i++) {
			for (int j = 0; j < net.size(i); j++) {
				Node n = net.getNode(i, j);
				for (int k = 0; k < n.size(); k++) {
					Node m = n.getConnection(k);
					double[][] e = {{n.getCoords()[0], n.getCoords()[1], m.getCoords()[0],
							m.getCoords()[1], n.getCoords()[0]-m.getCoords()[0],
							n.getCoords()[1]-m.getCoords()[1]},{n.getWeight(k)},{i}};
					//double[][] e = {{Math.sqrt(Math.pow(n.getCoords()[0]-m.getCoords()[0],2)+
					//		Math.pow(n.getCoords()[1]-m.getCoords()[1],2))}, {n.getWeight(k)},{i}};
					//double[][] e = {{n.getCoords()[0]-m.getCoords()[0],
					//    n.getCoords()[1]-m.getCoords()[1]/*, Math.floor((
					//		n.getCoords()[0]/10.0)*3), Math.floor((n.getCoords()[1]/10.0)*3)*/},
					//		{n.getWeight(k)},{i}};
					d.add(e);
				}
			}
		}
		double[][][] r = new double[d.size()][][];
		return d.toArray(r);
	}
	
  /*Both functions are fixed to only process the first layer*/

	//Use CPPN c to set the weights of the substrate net
	public static void setSubstrateWeights(NeuralNet c, NeuralNet net){
		for (int i = 0; i < 1; i++) {
			for (int j = 0; j < net.size(i); j++) {
				Node n = net.getNode(i, j);
				for (int k = 0; k < n.size(); k++) {
					Node m = n.getConnection(k);
					double[] input = {n.getCoords()[0], n.getCoords()[1], 
							m.getCoords()[0], m.getCoords()[1], n.getCoords()[0]-m.getCoords()[0],
							n.getCoords()[1]-m.getCoords()[1]};
					//double[] input = {Math.sqrt(Math.pow(n.getCoords()[0]-m.getCoords()[0],2)+
					//		Math.pow(n.getCoords()[1]-m.getCoords()[1],2))};
					//double[] input = {n.getCoords()[0]-m.getCoords()[0],
					//		n.getCoords()[1]-m.getCoords()[1]/*, Math.floor((
					//		n.getCoords()[0]/10.0)*3), Math.floor((n.getCoords()[1]/10.0)*3)*/};
					
					c.processInput(input);
					n.setWeight(k, c.getOutputs()[i]);
				}
			}
		}
	}
}
